import io
import re
import sys
import os
import logging

# 添加本地库路径
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib')))


import fitz  # PyMuPDF

from PIL import Image
from typing import Dict, Union, Optional
from fontTools.ttLib import TTFont
from PIL import ImageFont

# 配置日志记录器
logging.basicConfig(level=logging.INFO,  # 设置日志级别
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',  # 设置日志格式
                    datefmt='%Y-%m-%d %H:%M:%S')  # 设置日期和时间格式
 
# 获取一个日志记录器对象（通常使用__name__来获取当前模块的名称作为日志器的名称）
logger = logging.getLogger(__name__)
 
class PDFPlaceholderReplacer:
    """PDF 占位符替换引擎"""
    
    def __init__(self, font_dir="fonts"):
        self.fonts = {}
        self._load_fonts(font_dir)
        self.default_font_size=12

    def _load_fonts(self, font_dir):
        """加载字体并建立字符映射表"""
        if not os.path.exists(font_dir):
            return

        for font_file in os.listdir(font_dir):
            if not font_file.lower().endswith(('.ttf', '.otf')):
                continue

            font_path = os.path.join(font_dir, font_file)
            font_name = os.path.splitext(font_file)[0]
            font_path = os.path.abspath(font_path)  # 转换为绝对路径
            try:
                # 解析字体
                ttfont = TTFont(font_path)
                cmap = set()
                for table in ttfont['cmap'].tables:
                    if table.isUnicode():
                        cmap.update(table.cmap.keys())
                font_name = ttfont['name'].getDebugName(6)  # 获取PostScript名称
                self.fonts[font_name] = {
                    "path": font_path,
                    "cmap": cmap
                }
                logger.info(f"字体{font_path}加载成功: {font_name} (支持字符数: {len(cmap)})")
            except Exception as e:
                logger.error(f"字体 {font_file} 加载失败: {str(e)}")
                
    def _get_font_cmap(self, font_path):
        """获取字体支持的Unicode字符集合"""
        ttfont = TTFont(font_path)
        cmap = set()
        for table in ttfont['cmap'].tables:
            if table.isUnicode():
                cmap.update(table.cmap.keys())
        return cmap
    
    def _split_text_by_font(self, text, default_font_cmap):
        """将文本分割为多个字体段"""
        segments = []
        current_font = "ZhiChunSong"
        current_segment = ""
        
        for char in text:
            code_point = ord(char)
            
            # 检查是否为生僻字（默认字体不支持）
            if code_point not in default_font_cmap:
                # 查找第一个支持该字符的备用字体
                for font_name, font_data in self.fonts.items():
                    if font_name == "ZhiChunSong":
                        continue
                    if code_point in font_data["cmap"]:
                        target_font = font_name
                        break
                else:
                    target_font = "ZhiChunSong"  # 所有字体都不支持
            else:
                target_font = "ZhiChunSong"
                
            # 切换字体段
            if target_font != current_font:
                if current_segment:
                    segments.append((current_font, current_segment))
                current_font = target_font
                current_segment = char
            else:
                current_segment += char
                
        if current_segment:
            segments.append((current_font, current_segment))
            
        return segments

    def process_pdf(
        self,
        pdf_bytes: bytes,
        replacements: Dict[str, str],
    ) -> bytes:
        """
        处理 PDF 并返回修改后的字节
        
        :param pdf_bytes: 原始 PDF 字节数据
        :param replacements: 替换规则字典，格式示例:
            {
                "name": "value"#@@#"fontsize",
                "custSignImg": "imgpath"#@@@#"width"#@@@#"height",
                "bankSignImg": "imgpath"#@@@#"width"#@@@#"height"
            }
        :return: 处理后的 PDF 字节
        """
        doc = fitz.open(stream=pdf_bytes, filetype="pdf")
        placeholder_pattern = re.compile(r"\$\{([^{}]+)\}")

        for page in doc:
            self._process_page(page, placeholder_pattern, replacements)

        output_buffer = io.BytesIO()
        doc.save(output_buffer)
        doc.close()
        return output_buffer.getvalue()

    def _process_page(self, page, pattern, replacements):
        """处理单个页面"""
        text = page.get_text("text")
        logger.info(f"检索单页pdf {text} ")
        matches = pattern.findall(text)
        #text_instances = page.search_for(r"\$\{([^{}]+)\}")
        #text_instances.sort(key=lambda x: (-x.y0, x.x0))

        #for rect in text_instances:
        imgs=[]
        for rect in matches:
            #placeholder_text = page.get_textbox(rect).strip()
            #match = pattern.match(placeholder_text)
            #logger.info(f"检索匹配占位符 {match} ")
            #if not match:
            #    continue
            #key = match.group(1)
            logger.info(f"带替换占位符key： {rect} ")
            key = rect
            if key not in replacements:
                continue

            # 擦除原内容
            rect = page.search_for("${"+rect+"}")[0]
            page.add_redact_annot(rect)
            page.apply_redactions()

            replacement = replacements[key]
            if  replacement:
                if key in {"custSignImg", "bankSignImg"}:
                    logger.info(f"带替换占位符replacement： {replacement} ")
                    attributes = replacement.split("#@@@#")
                    img_path, width, height = attributes
                    imgs.append({"page":page,"rect":rect,"replacement":{"imgpath": img_path, "width": width, "height": height}})
                    #self._replace_image(page, rect, {"imgpath": img_path, "width": width, "height": height})
                else:
                    attributes = replacement.split("#@@#", 1)
                    textvalue = attributes[0].strip()  # 清理数据[‌:ml-citation{ref="6" data="citationList"}]
                    fontsize = attributes[1] if len(attributes) > 1 else "12"
                    self._replace_text(page, rect, {"textvalue": textvalue, "fontsize": fontsize})
        for img in imgs:
            self._replace_image(img.get("page"), img.get("rect"), img.get("replacement"))

    def _replace_text(self, page, rect, replacement):
        """文本替换（支持换行和精确垂直居中）"""
        doc = page.parent
        text = replacement["textvalue"].replace('\r\n', '\n')  # 统一换行符
        font_size = int(replacement.get("fontsize", self.default_font_size))
        
        # 获取默认字体信息
        default_font_name = "ZhiChunSong"
        if default_font_name not in self.fonts:
            raise ValueError("默认字体未找到")
        default_cmap = self.fonts[default_font_name]["cmap"]
        
        # 分割文本为多行
        lines = text.split('\n')
        
        # 预计算所有行信息
        line_data = []
        total_height = 0
        for line in lines:
            # 分割字体段
            segments = self._split_text_by_font(line, default_cmap)
            
            # 计算行度量
            line_width = 0
            max_ascent = 0
            max_descent = 0
            segment_info = []
            
            for font_name, text_segment in segments:
                # 获取字体路径
                font_data = self.fonts.get(font_name)
                if not font_data:
                    raise ValueError(f"字体 {font_name} 未加载")
                
                # 获取字体度量
                pil_font = ImageFont.truetype(font_data["path"], font_size)
                ascent, descent = pil_font.getmetrics()
                text_width = pil_font.getlength(text_segment)
                
                # 更新行度量
                max_ascent = max(max_ascent, ascent)
                max_descent = max(max_descent, descent)
                line_width += text_width
                
                segment_info.append({
                    "font_name": font_name,
                    "text": text_segment,
                    "width": text_width,
                    "ascent": ascent,
                    "font_path": font_data["path"]
                })
            
            line_height = max_ascent + max_descent
            total_height += line_height
            line_data.append({
                "segments": segment_info,
                "width": line_width,
                "ascent": max_ascent,
                "height": line_height
            })
        
        # 计算垂直起始位置（整体垂直居中）
        y_position = rect.y0 + (rect.height - total_height) / 2
        
        # 逐行渲染
        for line in line_data:
            # 计算水平起始位置（行水平居中）
            x_position = rect.x0 + (rect.width - line["width"]) / 2
            
            # 渲染行内片段
            for segment in line["segments"]:
                # 确保字体嵌入
                try:
                    page.insert_font(
                        fontname=segment["font_name"],
                        fontfile=segment["font_path"],
                        set_simple=False
                    )
                except ValueError:
                    pass  # 字体已存在时忽略错误
                
                # 计算基线位置（y_position + 行ascent）
                baseline_y = y_position + line["ascent"]
                
                page.insert_text(
                    point=(x_position, baseline_y),
                    text=segment["text"],
                    fontname=segment["font_name"],
                    fontsize=font_size,
                    color=(0, 0, 0)
                )
                
                x_position += segment["width"]
            
            # 更新垂直位置
            y_position += line["height"]

        logger.info(f"文本替换完成：{text}")

    def _calculate_font_size(self, text, font_name, rect):
        """动态计算字号（示例简化）"""
        max_width = rect.width * 0.9  # 保留10%边距
        font_size = 12
        
        while font_size > 6:
            text_width = fitz.get_text_length(text, fontname=font_name, fontsize=font_size)
            if text_width <= max_width:
                break
            font_size -= 1
            
        return font_size
        
    def _replace_image(self, page, rect, replacement):
        """图片替换"""
        try:
            img_path = replacement["imgpath"]
            width = int(replacement["width"])
            height = int(replacement["height"])
            with open(img_path, "rb") as f:
                img_data = f.read()
            # 加载图片
            img = Image.open(io.BytesIO(img_data))
            img_bytes = io.BytesIO()
            img.save(img_bytes, format="PNG")
            pixmap = fitz.Pixmap(img_bytes.getvalue())

            # 计算缩放尺寸
            target_width = width
            target_height = height
            scale_factor = min(
                target_width / pixmap.width,
                target_height / pixmap.height
            )
            new_width = pixmap.width * scale_factor
            new_height = pixmap.height * scale_factor

            # 居中定位
            x = rect.x0 + (rect.width - new_width) / 2
            y = rect.y0 + (rect.height - new_height) / 2

            page.insert_image(
                fitz.Rect(x, y, x + new_width, y + new_height),
                pixmap=pixmap,
            )
        except Exception as e:
            raise ValueError(f"图片处理失败: {str(e)}")

    def savePdf(self,outfilepath,output_bytes):
        # 带扩展名的文件名
        filename_with_ext = os.path.basename(outfilepath)
        # 不带扩展名的文件名
        filename_without_ext = os.path.splitext(outfilepath)
        # 获取文件路径
        dir_path = os.path.dirname(outfilepath)
         # 创建保存目录（如果不存在）
        os.makedirs(dir_path, exist_ok=True)
        filepath = os.path.join(dir_path, filename_with_ext)
        # 保存文件
        with open(filepath, "wb") as f:
            f.write(output_bytes)

# 使用示例
if __name__ == "__main__":
    # 初始化处理器
    processor = PDFPlaceholderReplacer()

    # 准备测试数据
    with open("template.pdf", "rb") as f:
        pdf_bytes = f.read()

    # 准备替换参数
    replacements = {
        "name": {
            "type": "text",
            "value": "张三\n技术总监",
            "font_size": 14
        },
        "logo": {
            "type": "image",
            "value": open("logo.png", "rb").read(),
            "scale": 0.9
        }
    }

    # 处理并保存
    output_bytes = processor.process_pdf(pdf_bytes, replacements)
    with open("output.pdf", "wb") as f:
        f.write(output_bytes)